/*
//   Copyright 2008 Dear Soft(http://dear-soft.com)
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
 */

package com.dear.soft.components.path;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.font.TextAttribute;
import java.text.AttributedCharacterIterator.Attribute;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;

public class BreadCrumb extends JPanel {
	private static final long serialVersionUID = 1L;
	private static final CellConstraints cc = new CellConstraints();
	private final List<PathChangeListener> listeners = Collections
			.synchronizedList(new ArrayList<PathChangeListener>());
	private final List<String> elements = Collections
			.synchronizedList(new ArrayList<String>());
	private final Map<Integer, JLabel> crumbs = Collections
			.synchronizedMap(new HashMap<Integer, JLabel>());
	private int used = 0;
	private int next = 1;
	private boolean startWithSeparator = false;
	private final String pathSeparator;
	private final JLabel separator;
	private final JLabel skipped;
	private final Font labelFont;
	private final JLabel rootLabel;

	public BreadCrumb(Color labelColor) {
		this("/", labelColor, 10, true);

	}

	public BreadCrumb(Color labelColor, int fontSize) {
		this("/", labelColor, fontSize, true);
	}

	public BreadCrumb() {
		this("/", Color.BLUE, 10, true);
	}

	public BreadCrumb(String pathSeparator, Color labelColor, int fontSize,
			boolean underline) {
		this.pathSeparator = pathSeparator;
		this.separator = new JLabel(pathSeparator);
		this.skipped = new JLabel("...");
		this.rootLabel = new JLabel("");
		this.rootLabel.addMouseListener(createLabelMouseListener(-1));
		Map<Attribute, Object> as = new HashMap<Attribute, Object>();
		if (underline) {
			as.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
		}
		as.put(TextAttribute.SIZE, fontSize);
		as.put(TextAttribute.FOREGROUND, labelColor);
		this.labelFont = separator.getFont().deriveFont(as);
		this.separator.setFont(labelFont);
		this.skipped.setFont(labelFont);
		this.rootLabel.setFont(labelFont);
		this.addComponentListener(getResizeListener());
	}

	private ComponentAdapter getResizeListener() {
		return new ComponentAdapter() {
			public void componentResized(ComponentEvent e) {
				int width = getSize().width;
				if (width > next) {
					if (used != next) {
						relayoutCrumbs();
					}
				} else {
					if (width < used) {
						relayoutCrumbs();
					}
				}
			}
		};
	}

	public void addPathChangeListener(PathChangeListener listener) {
		listeners.add(listener);
	}

	public void removePathChangeListener(PathChangeListener listener) {
		listeners.remove(listener);
	}

	protected void notifyPathChangeListener(String target) {
		for (PathChangeListener listener : listeners) {
			listener.pathChanged(target);
		}
	}

	public void setPath(String path) {
		elements.clear();
		startWithSeparator = path.startsWith(pathSeparator);
		for (String e : path.split(pathSeparator)) {
			if (e.length() > 0) {
				elements.add(e);
			}
		}
		rebuildGui();
	}

	public void setRoot(String root) {
		rootLabel.setText(root);
		rootLabel.setFont(labelFont);
	}

	private void rebuildGui() {
		crumbs.clear();

		for (int i = 0; i < elements.size(); i++) {
			JLabel label = new JLabel(elements.get(i));
			final int index = i;
			label.setFont(labelFont);
			label.addMouseListener(createLabelMouseListener(index));
			crumbs.put(index, label);
		}

		relayoutCrumbs();

	}

	private MouseAdapter createLabelMouseListener(final int index) {
		return new MouseAdapter() {

			@Override
			public void mouseClicked(MouseEvent e) {
				if (e.getClickCount() > 0) {
					onIndexSelected(index);
				}
			}

			@Override
			public void mouseEntered(MouseEvent e) {
				setCursor(new Cursor(Cursor.HAND_CURSOR));
			}

			@Override
			public void mouseExited(MouseEvent e) {
				setCursor(Cursor.getDefaultCursor());
			}
		};
	}

	private void relayoutCrumbs() {
		if (!EventQueue.isDispatchThread()) {
			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					relayoutCrumbs();
				}
			});
		}
		removeAll();
		int width = this.getSize().width;
		if (width == 0) {
			return;
		}

		this.used = this.skipped.getPreferredSize().width;
		int sep = separator.getPreferredSize().width;
		int leftIndex = 0;
		int rightIndex = crumbs.size();
		used += sep * 2 + rootLabel.getPreferredSize().width;
		this.next = used;
		StringBuffer format = new StringBuffer("p,p,p,p,p");
		for (int i = 0, j = crumbs.size() - 1; i <= j; i++, j--) {
			if (i > j) {
				break;
			}
			int left = crumbs.get(i).getPreferredSize().width;
			int right = crumbs.get(j).getPreferredSize().width;
			next += left;
			if (next > width) {
				break;
			}
			format.append(",p,p");
			used += left;
			leftIndex = i;

			if (i < j) {
				next += right;
				if (next > width) {
					break;
				}
				used += right;
				format.append(",p,p");
				rightIndex = j;
			}
		}
		setLayout(new FormLayout(format.toString(), "p"));

		add(rootLabel, cc.xy(1, 1, "l,f"));
		addSeparator(2);

		if (crumbs.size() > 0) {
			int x = 3;
			for (int ii = 0; ii <= leftIndex; ii++) {
				add(crumbs.get(ii), cc.xy(x, 1, "l,f"));
				addSeparator(x + 1);
				x += 2;
			}
			if ((leftIndex + crumbs.size() - rightIndex + 1) != crumbs.size()) {
				JLabel nsep = new JLabel("...");
				nsep.setFont(labelFont);
				add(nsep, cc.xy(x, 1, "l,f"));
				x += 1;
			}
			for (int ii = rightIndex; ii <= (crumbs.size() - 1); ii++) {
				add(crumbs.get(ii), cc.xy(x, 1, "l,f"));
				addSeparator(x + 1);
				x += 2;
			}
		}
		validate();
		repaint();
	}

	private void addSeparator(int x) {
		JLabel nsep = new JLabel(pathSeparator);
		nsep.setFont(labelFont);
		add(nsep, cc.xy(x, 1, "l,f"));
	}

	private void onIndexSelected(int index) {
		notifyListeners(getPathIndex(index));
	}

	private String getPathIndex(int index) {
		if (index < 0) {
			return "";
		}
		StringBuffer path = new StringBuffer();
		if (startWithSeparator) {
			path.append(pathSeparator);
		}
		for (int i = 0; i <= index; i++) {
			path.append(elements.get(i));
			if (i != index) {
				path.append(pathSeparator);
			}
		}
		return path.toString();
	}

	public String getPath() {
		return getPathIndex(elements.size() - 1);
	}

	protected void notifyListeners(String target) {
		for (PathChangeListener listener : listeners) {
			listener.pathChanged(target);
		}
	}
}
